/**
 * 
 */
package ro.dta.idbi.patterns;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ro.dta.idbi.interfaces.IExtentGenerator;
import ro.dta.idbi.interfaces.IntegrationOperation;
import ro.dta.idbi.model.AbstractPattern;
import ro.dta.idbi.model.AutoMedUtils;
import ro.dta.idbi.model.Utils;
import uk.ac.ic.doc.automed.IntegrityException;
import uk.ac.ic.doc.automed.NotFoundException;
import uk.ac.ic.doc.automed.modelmanagement.modeldef.SQLModelDef;
import uk.ac.ic.doc.automed.reps.Schema;
import uk.ac.ic.doc.automed.reps.SchemaObject;

/**
 * <b>Introduction of total generalisation pattern</b><br />
 * 
 * Example of use:<br />
 * 
 * <code>table_total_generalisation (&lt;&lt;newTable&gt;&gt;,&lt;&lt;table1&gt;&gt;,...,&lt;&lt;tablen&gt;&gt;)</code>
 * <br />
 * 
 * Adds <i>newTable</i> as the supertype of <i>table1</i> ... <i>tablen</i>
 * 
 * @author Tudor Dobrila
 * 
 */
public class TableGener extends AbstractPattern {

	/**
	 * Name of the pattern
	 */
	public static final String NAME = "Addition of Union";

	/**
	 * Description of the pattern
	 */
	public static final String DESCRIPTION = "Introduce a new table as the supertype of the selected tables";

	/**
	 * Name of the command associated with the pattern
	 */
	public static final String COMMAND = "table_total_generalisation";

	/**
	 * Integration phase during which the command is applied
	 */
	public static final EnumSet<IntegrationOperation> OPERATION = EnumSet.of(
			IntegrationOperation.CONFORMING, IntegrationOperation.MERGING);

	/**
	 * Name of the parent table being introduced
	 */
	private transient String newTableStr;

	/**
	 * List of child tables
	 */
	private transient List<String> origTables;

	/**
	 * Introduction of total generalisation pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Source schema over which the transformation is applied
	 */
	public TableGener(final Schema sourceSchema) throws NotFoundException, IntegrityException {
		super(sourceSchema);

		pattern = Pattern.compile("^" + COMMAND + "[ ]*\\((" + TABLE_REGEX + ")" + COMMA_REGEX
				+ TABLE_SEQ_REGEX + "\\)$");
	}

	/**
	 * Introduction of total generalisation pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Name of source schema over which the transformation is applied
	 */
	public TableGener(final String sourceSchema) throws NotFoundException, IntegrityException {
		this(Schema.getSchema(sourceSchema));
	}

	/**
	 * Introduction of total generalisation pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Source schema over which the transformation is applied
	 * @param newTableStr
	 *            Name of the parent table being introduced
	 * @param origTables
	 *            List of child tables
	 */
	public TableGener(final Schema sourceSchema, final String newTableStr,
			final List<String> origTables) throws NotFoundException, IntegrityException {
		this(sourceSchema);

		this.newTableStr = newTableStr;
		this.origTables = origTables;
	}

	@Override
	protected List<String> getPrimitiveCommands() throws NotFoundException, IntegrityException {
		List<String> result = new ArrayList<String>();

		if (origTables.size() <= 1) {
			throw new IllegalArgumentException("Can only apply pattern to two or more tables.");
		}

		SchemaObject[] origTablesObj = new SchemaObject[origTables.size()];
		for (int i = 0; i < origTablesObj.length; i++) {
			origTablesObj[i] = sourceSchema.getSchemaObject("<<" + origTables.get(i) + ">>");
		}

		final String table1Str = origTables.get(0);
		final SchemaObject table1 = sourceSchema.getSchemaObject("<<" + table1Str + ">>");
		final List<String> pks = AutoMedUtils.getPK(sourceSchema, table1);
		final String pkStr = AutoMedUtils.getPKString(sourceSchema, table1);
		final ExtentGenerator gen = new ExtentGenerator(origTables, pks, pkStr);

		if (!gen.checkPK(sourceSchema)) {
			throw new IllegalArgumentException("The PKs in the tables have to be the same.");
		}

		final StringBuilder cmd = new StringBuilder();

		// Step 1: create the supertype table
		final String iql = gen.getExtentsForStep(1).get(0);
		cmd.append("add (");
		cmd.append(Utils.genTableRepresentation(newTableStr));
		cmd.append(",").append(iql).append(")");
		result.add(cmd.toString());

		// Step 2: add the PK columns to the supertype table
		Object[] pkArr = new Object[pks.size() + 2];
		pkArr[0] = newTableStr + "_pk";
		pkArr[1] = newTableStr;
		int crtPos = 2;
		final List<String> extents = gen.getExtentsForStep(2);
		for (int i = 0; i < pks.size(); i++) {
			final String pk = pks.get(i);
			final SchemaObject pkObj = sourceSchema.getSchemaObject("<<" + table1Str + "," + pk
					+ ">>");
			final Object[] pkDef = pkObj.getSchemeDefinition();
			cmd.setLength(0);
			cmd.append("add (");
			cmd.append(Utils.genColRepresentation(newTableStr, pk, pkDef[2], pkDef[3]));
			cmd.append(",").append(extents.get(i)).append(")");
			result.add(cmd.toString());

			pkArr[crtPos++] = "<<" + newTableStr + "," + pk + ">>";
		}

		// Step 3: add the PK constraint to the newly created table
		if (sqlModel.isFeatureInUse(SQLModelDef.PRIMARY_KEY)) {
			cmd.setLength(0);
			cmd.append("add (");
			cmd.append(Utils.genPKRepresentation(pkArr));
			cmd.append(")");
			result.add(cmd.toString());
		}

		// Step 4: add the FK constraints from the original tables to the newly
		// created table
		if (sqlModel.isFeatureInUse(SQLModelDef.FOREIGN_KEY)) {
			for (int i = 0; i < origTables.size(); i++) {
				final String crtTbl = origTables.get(i);
				cmd.setLength(0);
				cmd.append("add (");
				cmd.append(Utils.genFKRepresentation(crtTbl, newTableStr, pks));
				cmd.append(")");
				result.add(cmd.toString());
			}
		}

		return result;
	}

	@Override
	protected boolean verify() {
		// TODO Auto-generated method stub
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ro.dta.idbi.model.TransformationPattern#parse(java.lang.String)
	 */
	@Override
	protected void parse(final String trans) {
		final Matcher matcher = pattern.matcher(trans);
		matcher.find();

		newTableStr = parseTableName(matcher.group(1));
		origTables = new ArrayList<String>();
		final String[] tokens = matcher.group(2).split(",");
		for (String s1 : tokens) {
			origTables.add(parseTableName(s1.trim()));
		}
	}

	/**
	 * Extent generator for the <i>Introduction of total generalisation
	 * pattern</i>
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class ExtentGenerator implements IExtentGenerator {
		private final transient List<String> origTables;
		private final transient String pkStr;
		private final transient List<String> pks;

		public ExtentGenerator(final List<String> origTables, final List<String> pks,
				final String pkStr) {
			this.origTables = origTables;
			this.pks = pks;
			this.pkStr = pkStr;
		}

		public List<String> getExtentsForStep(final int step) {
			final List<String> result = new ArrayList<String>();
			final StringBuilder iql = new StringBuilder();
			switch (step) {
			case 1:
				// Step 1: create the supertype table
				for (String tbl : origTables) {
					iql.append("<<").append(tbl).append(">> ++ ");
				}
				iql.setLength(iql.length() - 4);
				result.add(iql.toString());
				break;
			case 2:
				// Step 2: add the PK columns to the supertype table
				for (String pk : pks) {
					iql.setLength(0);
					for (String tbl : origTables) {
						iql.append("[ {").append(pkStr).append(",").append(pk).append("} | {");
						iql.append(pkStr).append(",").append(pk).append("} <- <<");
						iql.append(tbl).append(",").append(pk).append(">> ] ++ ");
					}
					iql.setLength(iql.length() - 4);
					result.add(iql.toString());
				}
				break;
			default:
			}
			return result;
		}

		/**
		 * Check if the Primary Keys in the original tables are the same (name
		 * and type)
		 * 
		 * @return True if primary keys are the same, false otherwise
		 */
		public boolean checkPK(final Schema sourceSchema) throws NotFoundException {
			final String table1Str = origTables.get(0);
			final SchemaObject table1 = sourceSchema.getSchemaObject("<<" + table1Str + ">>");
			final List<String> pks = AutoMedUtils.getPK(sourceSchema, table1);
			final Object[] pkTypes = new Object[pks.size()];

			for (int i = 0; i < pks.size(); i++) {
				final String colName = "<<" + table1Str + "," + pks.get(i) + ">>";
				pkTypes[i] = sourceSchema.getSchemaObject(colName).getSchemeDefinition()[3];
			}

			// Verify that all other tables have the same primary key
			for (int i = 1; i < origTables.size(); i++) {
				final String crtTblStr = origTables.get(i);
				final SchemaObject crtTbl = sourceSchema.getSchemaObject("<<" + crtTblStr + ">>");
				final List<String> crtPKs = AutoMedUtils.getPK(sourceSchema, crtTbl);

				if (pks.size() != crtPKs.size()) {
					// Not the same no. of primary keys
					return false;
				}

				for (String pk : crtPKs) {
					if (!pks.contains(pk)) {
						// Tables do not contain the SAME primary keys
						return false;
					}

					final int index = pks.indexOf(pk);
					final String crtColStr = "<<" + crtTblStr + "," + pk + ">>";
					final SchemaObject crtCol = sourceSchema.getSchemaObject(crtColStr);
					final Object crtType = crtCol.getSchemeDefinition()[3];

					if (!crtType.toString().equals(pkTypes[index].toString())) {
						return false;
					}
				}
			}

			return true;
		}
	}
}
